Abstract
Con la intención de definir un punto de análisis serio y congruente con la realidad del mercado, así como de la propia aplicación de los modelos propuestos, además de brindar también una serie de herramientas computacionales que apoyen a la experimentación y pruebas que se deseen aplicar al conjunto de datos de interés. Se busca ir dando un análisis guiado, donde se pueda ir visualizando la evolución y desempeño del modelo de **APT** y **Roll** respectivamente. Con el trabajo realizado se encontró que en términos de la microestructura del mercado de _BTCUSDT_ a medida que se toman los puntos donde la mejor voluntad de compra y la mejor voluntad de venta estan mas cerca de coincidir las probabilidades de que exista un cambio en precio aumenta. Otro punto de análisis que surge del presente trabajo se da a partir de la relación que existe entre los tipos de órdenes, se demostró matemáticamente como es que existe una relación lineal directa entre cada tipo de orden, de manera que una orden de compra tiende a seguir otra orden previa de compra y analogamente para las ventas.
El desarrollo de este proyecto responde a la pregunta natural que surge en la definición de cada modelo matemático, ¿cómo es que se logrará desempeñar en un contexto de la vida real?. Como es de conocimiento común, un modelo tiene como finalidad principal ser un reflejo lo más simplificado (pero significativamente) posible sobre el comportamiento de algún fenómeno de interés, tratar de modelar en su totalidad la realidad que envuelve a dicho objeto de estudio es una tarea sumamente compleja. En este sentido este trabajo responde a dicha interrogante aplicado directamente a $2$ de los principales modelos de alta frecuencia utilizados en el trading algoritmico, el modelo de APT y el modelo de Roll, los puntos de análisis propuestos corresponden básicamente a estar probando no solo resultados finales sino también algunos de los supuestos que envuelven el desarrollo de los mismos, a través de datos correspondientes a las órdenes planteadas sobre el BTCUSDT, un activo financiero bastante interesante y característico por su alta volatilidad presente en los mercados financieros del mundo.
Para poder correr el presente notebook es necesario haber instalado todas aquellas librerias que se encuentran descritas en el archivo adjunto en el repositorio con nombre requirements.txt. Dichas dependencias son las siguientes:
Además de todas las librerias previamente mencionadas, el correcto funcionamiento de este notebook también requiere de algunos datos externos. Estos archivos se encuentran directamente en la carpeta files adjunta dentro de este mismo repositorio:
En caso de que no se hayan instalado los paquetes dentro del ambiente virtual sobre el cual se está trabajando, se pueden instalar directamente dentro de este cuaderno. A continuación se encuentra la estructura de comandos necesarias para instalar las librerias, solo correr en caso de que no se hayan instalado previamente:
%%capture
# Install all the pip packages in the requirements.txt
import sys
!{sys.executable} -m pip install -r requirements.txt
Ya con los requerimentos computacionales bien definidos, ahora se proseguirá a cargar tanto las librerias como los scripts de python 3 para poder realizar el análisis correspondiente.
### Required packages
import pandas as pd
import numpy as np
import warnings
### Required local scripts
import data as dt
import functions as fn
import visualizations as vz
Para poder realizar este laboratorio en particular, se nos fueron proporcionados los datos previamente mencionados. A continuación se comenzará a describir un poco más a detalle la estructura de los mismos para entender de mejor manera la materia prima sobre la cual se estará trabajando.
Order Book.
Este conjunto de datos se encuentra bajo el formato json, por tanto se compone por una lista de diccionarios donde cada subconjuto de valores responde a un libro de órdenes del activo con clave de pizarra BTCUSDT, dicho orderbook se encuentra asociado a cada punto del tiempo que representa la llave del diccionario. La actualización de dichos libros se da en términos de segundos, por lo tanto la ventana de tiempo sobre la cual trabajaremos es de un poco más de $1$ hora. A pesar de que pareciera que no tenemos tantos datos, la realidad es que cada libro cuenta con una cantidad suficiente de datos para poder extraer información relevante a ese punto del tiempo que se encuentra asociado.
Public Trades.
Este segundo y último conjunto de datos se encuentra en formato csv, y básicamente hace referencia a los movimientos de mercado (trades) que se realizan en cada punto del tiempo, nuevamente aquí estamos trabajando con aquellos datos donde hubo una conexión entre la parte compradora y la parte vendedora del mercado para el activo BTCUSDT, es decir, en estos nuevos datos contamos con la información de aquellas órdenes que sí fueron empatadas por lo tanto estas operaciones que se efectuan son las que al final del día terminan teniendo un impacto directo en la fluctuación del precio para el activo analizado. La ventana de tiempo para la cual disponemos de esta información se encuentra alrededor de $1$ día.
Contamos entonces con una lista de diccionarios donde cada llave representa un punto en el tiempo donde se contaba con ese determinado libro de órdenes, ahora lo que haremos será ir definiendo y mostrando un poco mas sobre los datos ya desde una perspectiva un poco más visual.
### Let's visualize the keys range.
ob_data = dt.ob_data_bit
range_ob = pd.to_datetime(pd.Series(list(ob_data.keys()))).sort_values()
print(f'El primer tiempo registrado está en: {range_ob[0]}, y el último en: {range_ob.tail(1).item()}')
El primer tiempo registrado está en: 2021-07-05 13:06:46.571000+00:00, y el último en: 2021-07-05 14:06:46.412000+00:00
Como podemos observar ya directamente de la descripción de los rangos para las llaves de los orderbooks contamos con un poco menos de $1$ hora de información correspondiente al día 2021-07-05 entre $1$ y $2$ de la tarde.
### Let's see how many books do we have
print(f'En una hora de tiempo contamos con {len(list(ob_data.keys())):,} libros')
En una hora de tiempo contamos con 2,401 libros
Entonces si bien pareciera que $1$ hora de tiempo no puede aportar mucha información, la realidad es que dentro de este horario contamos con alrededor de $2,401$ libros. A continuación se mostrará un poco respecto al contenido de cada orderbook, esto con la intención de alcanzar a percibir la cantidad de datos que tenemos.
### Let's see the first orderbook values
print(f'Este primer orderbook corresponde al punto en el tiempo. {range_ob[0]}:')
list(ob_data.values())[0].head(5)
Este primer orderbook corresponde al punto en el tiempo. 2021-07-05 13:06:46.571000+00:00:
| bid_size | bid | ask | ask_size | |
|---|---|---|---|---|
| 0 | 0.000400 | 28270.0 | 28275.0 | 0.025405 |
| 1 | 0.009787 | 28269.0 | 28276.0 | 0.516810 |
| 2 | 0.008168 | 28268.0 | 28277.0 | 0.005044 |
| 3 | 0.995787 | 28266.0 | 28278.0 | 0.377374 |
| 4 | 1.038704 | 28265.0 | 28280.0 | 1.179715 |
Como podemos percibir del dataframe correspondiente al primer punto del tiempo, contamos con diferentes atributos:
Todas y cada una de las llaves correspondientes a los datos del orderbook contienen un dataframe con las mismas características que se encuentra desplegado. Entonces a partir de estos valores es que se estará experimentando y observando la eficacia de los modelos de alta frecuencia propuestos.
Los trades públicos de cierta manera se encuentran contenidos en una estructura de datos mucho más amigable para la mayoría (csv), por tanto python se encarga de leer estos archivos y directamente transformarlos en un dataframe. A continuación observaremos un poco respecto a los atributos y dimensionas que trae consigo este archivo.
### Let's visualize the df range.
pt_data = dt.pt_data
range_pt = pd.to_datetime(pt_data.timestamp).sort_values()
print(f'El primer tiempo registrado está en: {range_pt.head(1).item()}, y el último en: {range_pt.tail(1).item()}')
El primer tiempo registrado está en: 2022-05-10 00:00:12, y el último en: 2022-05-10 23:59:38
Como podemos observar directamente de los rangos de tiempo para los public trades, vemos que el primer registro se efectuó en la madrugada del 2022-05-10 y el último de ellos se dió el mismo día justo antes de que termine, por lo tanto para este conjunto de información contamos con todo un día de transacciones.
### Let's see the public trades dataframe
print(f'Los public trades con los que contamos tienen un total de {pt_data.shape[0]:,} filas')
pt_data.head()
Los public trades con los que contamos tienen un total de 286,600 filas
| timestamp | price | amount | side | |
|---|---|---|---|---|
| 0 | 2022-05-10 08:08:07 | 31702.47 | 0.00035 | sell |
| 1 | 2022-05-10 08:08:07 | 31702.48 | 0.00263 | buy |
| 2 | 2022-05-10 08:08:07 | 31702.48 | 0.00631 | buy |
| 3 | 2022-05-10 08:08:07 | 31702.48 | 0.00264 | buy |
| 4 | 2022-05-10 08:08:07 | 31702.48 | 0.01146 | buy |
Del registro de transacciones para el BTCUSDT contamos con un total de $286,000$ operaciones, las cuales se encuentran dividas a lo largo de un día, en cuanto a los atributos del data frame contamos con el tiempo, el precio al cual se hizo la conexión, el volumen transaccionado a dicho precio en ese punto del tiempo, y finalmente contamos con la estructura de venta o compra lo cual indica de que lado se efectuó la operación.
Con la intención de poder eficientar tiempos computacionales se decide trabajar sobre una serie de scripts de python 3, los cuales se encuentran anexos al repositorio aptmodel-lab2. En ellos se encuentra predefinida una serie de cálculos los cuales nos ayudarán a realizar el test y analizar el cumplimiento del modelo frente a situaciones de la vida real.
Antes de poder entrar en detalle respecto a la serie de pruebas realizadas para el APT, es importante tener claro el origen y la teoría dentrás de este modelo de alta frecuencia. En este sentido el modelo parte de su definición básica "poder encontrar el pago futuro respecto a un activo financiero que presenta flujos", de manera que la ecuación básica para partir es la siguiente:
$$x_{t+1}=p_{t+1}+d_{t+1}$$donde:
Ahora hasta el momento se encuentra definido el pago que se podría esperar en un momento $t+1$, sin embargo el concepto económico que nos ayudará a modelar el impacto de ese pago futuro se dará a través de la función de utilidad de los consumos actuales y futuros de un inversionista:
$$U(c_{t}, c_{t+1})=U(c_{t})+\beta U(c_{t+1})$$donde:
Dentro de estas dotaciones de consumo para el inversionista tanto en tiempo presente $(t)$ como tiempo futuro $(t+1)$ existen ciertas restricciones que limitan su consumo, de manera que:
$$c_{t} = e_{t}-p_{t}K$$$$c_{t+1} = e_{t+1}+x_{t+1}K$$donde:
En este sentido y con una definición más clara de la utilidad y como se modela o define para cada punto en el tiempo, la pregunta natural surge al decir, ¿qué puedo definir con eso?. Para ello es importante pensar en cuales son los incentivos al consumo de un agente económico, el más claro de ellos debería corresponder al alcanzar la satisfacción máxima con la cantidad de recursos mínimos necesarios, entonces podríamos comenzar a definir al modelo del APT como un problema de optimización (maximización) donde se busca obtener la mayor utilidad posible con un consumo de $K$ en el activo riesgoso.
$$max_{{K}} U(c_{t}) + E\big[\beta U(c_{t+1})\big]$$Donde podemos comenzar a observar la presencia del operador de esperanza matemática $(E[x])$ el cual surge de la necesidad de análisis de un proceso estocástico no conocido $(x_{t+1})$ por lo tanto para esta componente la mejor manera de aproximar su valor se da a través del propio valor esperado asociado a su función de densidad de probabilidad.
Para poder encontrar este punto critico se define a través del críterio de primer orden, donde se deriva nuestra expresión con respecto a $K$ y ese valor se iguala a $0$. Después de realizar dicho proceso se encuentra que:
$$p_{t} = E \bigg[\beta \frac{U'(c_{t+1}}{U'(c_{t})} x_{t+1} \bigg] \longrightarrow p_{t} = E [m_{t+1} x_{t+1}]$$donde:
Partiendo entonces de la definición de $x_{t+1} = p_{t+1}+d_{t+1}$ y retomando la ecuación del precio $p_{t}$ es a este punto donde los principales supuestos del modelo se empiezan a definir. Es importante recordar que el enfoque sobre el cual se define el APT en este contexto es sobre la microestructura del mercado, la cual maneja diferenciales de tiempo muy pequeños (en mercados lo suficientemente líquidos), es por ello que se pueden definir y dar congruencia a los siguientes supuestos:
Con la aplicación de estos supuestos finalmente obtenemos el resultado final para el precio en tiempo $t+1$
$$E \big[p_{t+1} \big] = p_t$$Es decir, la mejor estimación del precio que se puede esperar a un punto futuro en el tiempo (según el APT) es el precio actual. Este tipo de proceso estocástico es muy particular y se le conoce como martingala
En este sentido el APT nos menciona que ante la incertidumbre que pueda llegar a existir en periodos futuros de tiempo la zona segura siempre será la cotización más actual (si y solo si para periodos de tiempo muy pequeños). Con la intención de comprobar la eficiencia del modelo frente a una situación real de mercado se estará contrastando a través de los siguientes experimentos:
Experimento 1: Número de ocurrencias en el que $p_{t+1} = p_{t}$
Experimento 2: Número de ocurrencias en el que $t_{t+1} \neq p_{t}$
Y es a través de estos experimentos y pruebas que se definirá una proporción que nos ayude a entender y reflejar que tanto se cumple la definción formal del APT en una microestructura de mercado tan volátil como lo es la de Bitcoin. Es importante mencionar que para términos de prueba se definirá a $p_t$ a través de dos grandes maneras de calcular el precio de un activo, el Simple Mid-Price y el Weighted Mid-Price.
A continuación se mostrará el contenido y estructura de cada función desarrollada para el uso y aplicación de esta serie de experimentos para el modelo de APT, con ello se busca dotar al lector de un mejor conocimiento interno de las mismas.
### Let's see docstring for apt testing (all orders)
help(fn.apt_check_all)
Help on function apt_check_all in module functions:
apt_check_all(ob_data: dict) -> dict
Test APT model function (for all orders contained on each orderbook)
Parameters
----------
ob_data:dict (default:None) --> Required parameter
Input data from orderbook, it has to be a dict of data frames with the following structure:
'timestamp': Principal key, correspond to the timestamp associated to each orderbook
'bid_size': First column on each data frame, correspond to the bid volume associated to each bid price order
'bid': Second column of the data frame, correspond to the highest price a buyer is willing to buy
'ask': Third column of the data frame, correspond to the lowest price a seller is willing to sell
'ask_size': Fourth column of the data frame, correspond to the ask volume associated to each ask price order
Returns
-------
r_data: dict
Return data, it's a dict of data frames where each one of them contains the result of the proposing experiments
the dict of return follows the next structure:
'simple_mid_price': First key, contains all the experiments developed for APT model testing with simple
mid-price
'weighted_mid_price': Second key, contains all the experiments developed for APT model testing with weighted
mid-price
References
----------
[1] https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html
### Let's see docstring for apt testing (tob orders)
help(fn.apt_check_tob)
Help on function apt_check_tob in module functions:
apt_check_tob(ob_data: dict) -> dict
Test APT model function (just for top of the book orders contained on each orderbook)
Parameters
----------
ob_data:dict (default:None) --> Required parameter
Input data from orderbook, it has to be a dict of data frames with the following structure:
'timestamp': Principal key, correspond to the timestamp associated to each orderbook
'bid_size': First column on each data frame, correspond to the bid volume associated to each bid price order
'bid': Second column of the data frame, correspond to the highest price a buyer is willing to buy
'ask': Third column of the data frame, correspond to the lowest price a seller is willing to sell
'ask_size': Fourth column of the data frame, correspond to the ask volume associated to each ask price order
Returns
-------
r_data: dict
Return data, it's a dict of data frames where each one of them contains the result of the proposing experiments
the dict of return follows the next structure:
'simple_mid_price': First key, contains all the experiments developed for APT model testing with simple
mid-price
'weighted_mid_price_a': Second key, contains all the experiments developed for APT model testing with weighted
mid-price (the first way of calculation).
'weighted_mid_price_b': Second key, contains all the experiments developed for APT model testing with weighted
mid-price (the second way of calculation)
References
----------
[1] https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html
### Let's define the apt testing data frames.
apt_all = fn.apt_check_all(ob_data)
apt_tob = fn.apt_check_tob(ob_data)
print(f'The keys for apt model test with all orders are: {list(apt_all.keys())}')
print(f'The keys for apt model test with tob orders are: {list(apt_tob.keys())}')
The keys for apt model test with all orders are: ['simple_mid_price', 'weighted_mid_price'] The keys for apt model test with tob orders are: ['simple_mid_price', 'weighted_mid_price_a', 'weighted_mid_price_b']
Este primer enfoque que se propone analizar incluye todas las órdenes de todos los libros de órdenes presentes en el diccionario de entrada, es decir, para este primer punto de análisis queremos observar los cambios en los precios de prácticamente todas las potenciales operaciones de los orderbooks (aproximadamente unos $60,000$ registros a consolidar). La metodología que se propone es la misma que se menciona en la definición del modelo, se busca encontrar la cantidad de veces que el Mid-Price sigue un proceso estocástico tipo Martingala para cada minuto en el tiempo registrado.
Para este análisis se calcula es Simple Mid-Price el cual se define matemáticamente por la siguiente expresión:
$$\text{Mid-Price} = \frac{\text{bid} + \text{ask}}{2}$$Y es este valor el que se calcula para todas y cada una de las órdenes presentes en cada orderbook del rango de datos con el que se cuenta.
### Let's see the data frame for simple mid-price test
print(f'''The min proportion where the mid-price follows martingale process is {apt_all['simple_mid_price']['P. Exp 1'].min()}
and it happened on {apt_all['simple_mid_price']['P. Exp 1'].idxmin()}''')
print("")
print(f'''The mean proportion where the mid-price follows martingale process is:
{round(apt_all['simple_mid_price']['P. Exp 1'].mean(),4)*100}%''')
apt_all['simple_mid_price'].head()
The min proportion where the mid-price follows martingale process is 0.67 and it happened on 2021-07-05 13:31:00 The mean proportion where the mid-price follows martingale process is: 80.41%
| Exp 1 | Exp 2 | P. Exp 1 | P. Exp 2 | |
|---|---|---|---|---|
| Time | ||||
| 2021-07-05 13:06:00 | 177 | 47 | 0.79 | 0.21 |
| 2021-07-05 13:07:00 | 781 | 243 | 0.76 | 0.24 |
| 2021-07-05 13:08:00 | 799 | 200 | 0.80 | 0.20 |
| 2021-07-05 13:09:00 | 750 | 224 | 0.77 | 0.23 |
| 2021-07-05 13:10:00 | 789 | 235 | 0.77 | 0.23 |
Para esta primera estructura del experimento con Simple Mid-Price de todas las órdenes presentes podemos observar directamente del data frame resultante que en general las proporciones donde el precio sigue un proceso tipo martingala son relativamente altas, con un $80\%$ de las veces en promedio y con un valor mínimo que ronda en el $67\%$ en la consolidación por minutos.
Este resultado nos da un primer acercamiento al modelo y a su practicidad en la aplicación del mismo, sin embargo a falta de más pruebas no se puede concluir nada aún.
Para términos del Weighted Mid-Price se calculó a través de la siguiente expresión matemática:
$$\text{Weighted Mid-Price} = \bigg(\frac{\text{bv}}{\sum_{i=1}^{n}\big(\text{bv}_{i}+\text{av}_{i}\big)} \bigg) \text{ap} + \bigg(\frac{\text{av}}{\sum_{i=1}^{n}\big(\text{bv}_{i}+\text{av}_{i}\big)} \bigg) \text{bp}$$donde:
En este sentido y al estar tomando practicamente todos las órdenes presentes, la profundidad detonada por $i$ solo se define para su mismo nivel.
### Let's see the data frame for weighted mid-price test
print(f'''The max proportion where the mid-price follows martingale process is {apt_all['weighted_mid_price']['P. Exp 1'].max()}
and it happened on {apt_all['weighted_mid_price']['P. Exp 1'].idxmax()}''')
print("")
print(f'''The mean proportion where the mid-price follows martingale process is:
{round(apt_all['weighted_mid_price']['P. Exp 1'].mean(),4)*100}%''')
apt_all['weighted_mid_price'].head()
The max proportion where the mid-price follows martingale process is 0.01 and it happened on 2021-07-05 14:02:00 The mean proportion where the mid-price follows martingale process is: 0.02%
| Exp 1 | Exp 2 | P. Exp 1 | P. Exp 2 | |
|---|---|---|---|---|
| Time | ||||
| 2021-07-05 13:06:00 | 0 | 224 | 0.0 | 1.0 |
| 2021-07-05 13:07:00 | 0 | 1024 | 0.0 | 1.0 |
| 2021-07-05 13:08:00 | 0 | 999 | 0.0 | 1.0 |
| 2021-07-05 13:09:00 | 0 | 974 | 0.0 | 1.0 |
| 2021-07-05 13:10:00 | 0 | 1024 | 0.0 | 1.0 |
Ahora para el mismo enfoque pero definiendo a $p_t$ como el Weighted Mid-Price a primera instancia se logra percibir un cambio radical en los resultados obtenidos previamente para el Simple Mid-Price. La proporción máxima en cada punto del tiempo se encuentra alrededor del $1\%$ de ocasiones donde el precio sigue un proceso tipo martingala un reducción considerable del $66\%$ respecto al Simple Mid-Price probado anteriormente. Además la proporción promedio se ve reducida a un $0.02\%$ un valor muy cercano a $0$ que nos indicaría que el modelo del APT tiene muy poca efectividad en la estructura de datos probada.
En este sentido surge una pregunta natural, ¿por qué se visualiza un cambio tan drástico?. La realidad es que a pesar de que se esta manejando la misma estructura de datos en ambos casos e incluso a pesar de que el Weighted Mid-Price arroja un valor no tan alejado del Simple Mid-Price, la clave para entender este comportamiento está en el orderbook. Si recordamos cada libro de órdenes tiene su profundidad, y a medida que vamos "bajando" en dichos niveles podemos encontrar una mayor disparidad en la voluntades de compra y venta de los inversionistas, ahora esas voluntades no solo cambian de precio a precio, también lo hacen a nivel volumen y es justamente aquí donde se encuentra la principal razón de cambio, ya que el Simple Mid-Price al ser un promedio sencillo pondera de igual manera las órdenes de compra y de venta para definir el precio, mientras que el Weighted Mid-Price sí hace una diferenciación en este sentido al ser un promedio ponderado.
En este segundo enfoque de análisis se busca realizar un proceso análogo al previamente descrito, sin embargo, la principal diferencia para este punto se da en que ahora solo se utilizarán los Mid-Prices que se definen única y exclusivamente para el Top of the Book, es decir, esta nueva propuesta solo tomará un precio para cada libro de órdenes, de manera que el procesamiento de datos se reducirá considerablemente ya que ahora solo se contarán con $2,400$ registros a consolidar por minuto.
Para el cálculo de este Simple Mid-Price se define prácticamente la misma fórmula que se utiliza para todas las órdenes, la diferencia principal radica en que ahora solo se tomará el Top of the Book, en otras palabras este nuevo análisis solo tomará el mejor bid y el mejor ask presente en cada libro de órdenes.
En este sentido y tratando de llegar a un análisis comparativo entre los Mid-Prices del Top of the Book y el resto de las órdenes, podríamos comenzar a intuir que las proporciones donde se cumpla que el precio sigue una martingala con esta nueva estructura de análisis será menor, ya que al tomar siempre el Top of the Book podríamos decir que estaríamos más cerca de "definir" la tendencia del precio a cada punto del tiempo, y en ese sentido se podría esperar que las fluctuaciones en el precio fueran mayores en comparación a si tomamos todas las órdenes presentes.
### Now let's render the simple mid-price experiment just for tob
print(f'''The min proportion where the mid-price follows martingale process is {apt_tob['simple_mid_price']['P. Exp 1'].min()}
and it happened on {apt_tob['simple_mid_price']['P. Exp 1'].idxmin()}''')
print("")
print(f'''The mean proportion where the mid-price follows martingale process is:
{round(apt_tob['simple_mid_price']['P. Exp 1'].mean(),4)*100}%''')
apt_tob['simple_mid_price'].head()
The min proportion where the mid-price follows martingale process is 0.65 and it happened on 2021-07-05 13:52:00 The mean proportion where the mid-price follows martingale process is: 72.39%
| Exp 1 | Exp 2 | P. Exp 1 | P. Exp 2 | |
|---|---|---|---|---|
| Time | ||||
| 2021-07-05 13:06:00 | 6 | 3 | 0.67 | 0.33 |
| 2021-07-05 13:07:00 | 27 | 14 | 0.66 | 0.34 |
| 2021-07-05 13:08:00 | 31 | 9 | 0.78 | 0.22 |
| 2021-07-05 13:09:00 | 27 | 12 | 0.69 | 0.31 |
| 2021-07-05 13:10:00 | 30 | 11 | 0.73 | 0.27 |
### Let's see the mean proportion for experiment 1.
print(f'''With all orders we have a mean proportion of {round(apt_all['simple_mid_price']['P. Exp 1'].mean(),4)*100}%''')
print(f'''With tob orders we have a mean proportion of {round(apt_tob['simple_mid_price']['P. Exp 1'].mean(),4)*100}%''')
With all orders we have a mean proportion of 80.41% With tob orders we have a mean proportion of 72.39%
Para este segundo enfoque de los experimentos realizados, el cambio principal como se comentó radica en que ahora solo se tomarán órdenes del top of the book, las cuales tienen la característica de representar el "mejor" Bid y el "mejor" Ask de cada libro de órdenes. En este sentido se esperaría que exista una diferencia de precios un poco más notoria de libro a libro, situación que se ve reflejada directamente en los experimentos realizados, donde ahora la proporción mínima donde el precio sigue un proceso tipo martingala se encuentra en $65\%$ con un promedio de éxito para esta prueba de $72\%$, es decir, un $8\%$ menos en comparación con el símil con todas las órdenes disponibles.
Este cambio se justifica principalmente por lo previamente comentado, al nosotros estar trabajando con las voluntades de compra y venta con mayor cercanía podemos decir que estamos un poco más cerca de "definir" la tendencia en el precio, y lógicamente ese cambio se ve más reflejado en comparativa a si tomamos todas las órdenes disponibles de cada orderbook, no por mucho pero si se logra distinguir esa pequeña diferenciación.
Como sabemos el Weigthed Mid-Price tiene diferentes perspectivas y maneras de calcularse, sin embargo, por definición se esperaría que si bien las escalas o incluso las unidades entre las diversas metodologías de cálculo puede variar dependiendo el caso, en teoría o al menos la hipótesis que se plantea es que no debería existir un cambio radical en cuanto a comportamiento y el reflejo de mercado que cada una de ellas aporta.
Para poder corroborar dicha situación se utilizarán los mismos experimentos definidos para el modelo de APT. Este primer enfoque con el cual se propone calcular el Weighted Mid-Price se define matemáticamente a través de la siguiente fórmula:
$$\text{Weighted Mid-Price} = \bigg(\frac{\sum_{i=1}^{n} \text{bv}_{i}}{\sum_{i=1}^{n} \text{bv}_{i}+\text{av}_{i}} \bigg) \bigg(\frac{\text{bp} + \text{ap}}{2} \bigg)$$donde:
En otras palabras esta manera de calcular el Weighted Mid-Price básicamente consiste en multiplicar el inbalance del libro de órdenes y multiplicarlo por el Simple Mid-Price.
### Let's see now the tob experiments for the first way of calculation of weighted mid-price
print(f'''The min proportion where mid-price follows martingale process is {apt_tob['weighted_mid_price_a']['P. Exp 1'].min()}
and it happened on {apt_tob['weighted_mid_price_a']['P. Exp 1'].idxmin()}''')
print("")
print(f'''The mean proportion where the mid-price follows martingale process is:
{round(apt_tob['weighted_mid_price_a']['P. Exp 1'].mean(),4)*100}%''')
apt_tob['weighted_mid_price_a'].head()
The min proportion where mid-price follows martingale process is 0.65 and it happened on 2021-07-05 13:08:00 The mean proportion where the mid-price follows martingale process is: 66.92%
| Exp 1 | Exp 2 | P. Exp 1 | P. Exp 2 | |
|---|---|---|---|---|
| Time | ||||
| 2021-07-05 13:06:00 | 6 | 3 | 0.67 | 0.33 |
| 2021-07-05 13:07:00 | 27 | 14 | 0.66 | 0.34 |
| 2021-07-05 13:08:00 | 26 | 14 | 0.65 | 0.35 |
| 2021-07-05 13:09:00 | 26 | 13 | 0.67 | 0.33 |
| 2021-07-05 13:10:00 | 27 | 14 | 0.66 | 0.34 |
Si comparamos el resultado obtenido con respecto al experimento realizado para todas las órdenes registradas vemos que existe una diferencia muy significativa ya que recordemos que para el caso del primero se contaba con un proporción promedio de cumplimiento del modelo de aproximadamente $0.02\%$, valor que si contrastamos con este nuevo vemos que existe una mejora del $64\%$.
Entonces se había comentado anteriormente que al nosotros estar trabajando con órdenes del top of the book podríamos llegar a esperar un mayor cambio o movimiento en el precio de libro a libro, sin embargo, para el caso del Weigthed Mid-Price logramos percibir que mejora bastante en comparación al uso de todas las órdenes, ¿por qué?. Nuevamente para responder esta pregunta nos debemos remontar a la definición y estructura de cada orderbook, y recalcar que estamos trabajando con los "mejores" Bid y Ask respectivamente, y es esta la situación la que justamente se debe resaltar, porque si bien es más probable que haya una mayor diferencia en el precio de libro a libro, al estar tomando solo este punto donde existe la mejor voluntad entre compradores y vendedores, también podemos decir que en temas de volumen puede llegar a existir una mayor paridad en comparación a lo planteado con todas las órdenes.
Para esta segunda parte de la experimentación con el Weighted Mid-Price se tomó la siguiente definición matemática:
$$\text{Weighted Mid-Price} = \bigg(\frac{\text{bv}}{\sum_{i=1}^{n}\big(\text{bv}_{i}+\text{av}_{i}\big)} \bigg) \text{ap} + \bigg(\frac{\text{av}}{\sum_{i=1}^{n}\big(\text{bv}_{i}+\text{av}_{i}\big)} \bigg) \text{bp}$$donde:
La idea del desarrollo de esta manera alternativa de cálculo es demostrar si es que empirícamente existe una diferenciación importante entre los métodos de cálculo entre los Weighted Mid-Prices.
### Now let's compare it with the alternative way of calculating weighted mid-price
print(f'''The min proportion where mid-price follows martingale process is {apt_tob['weighted_mid_price_b']['P. Exp 1'].min()}
and it happened on {apt_tob['weighted_mid_price_b']['P. Exp 1'].idxmin()}''')
print("")
print(f'''The mean proportion where the mid-price follows martingale process is:
{round(apt_tob['weighted_mid_price_b']['P. Exp 1'].mean(),4)*100}%''')
apt_tob['weighted_mid_price_b'].head()
The min proportion where mid-price follows martingale process is 0.65 and it happened on 2021-07-05 13:08:00 The mean proportion where the mid-price follows martingale process is: 68.87%
| Exp 1 | Exp 2 | P. Exp 1 | P. Exp 2 | |
|---|---|---|---|---|
| Time | ||||
| 2021-07-05 13:06:00 | 6 | 3 | 0.67 | 0.33 |
| 2021-07-05 13:07:00 | 27 | 14 | 0.66 | 0.34 |
| 2021-07-05 13:08:00 | 26 | 14 | 0.65 | 0.35 |
| 2021-07-05 13:09:00 | 26 | 13 | 0.67 | 0.33 |
| 2021-07-05 13:10:00 | 27 | 14 | 0.66 | 0.34 |
Comparado los resultados obtenidos a este punto con respecto a los del experimento pasado, podemos observar que el cambio realmente no es muy drástico en términos de cumplimiento del modelo de APT, en promedio el cambio solo se refleja en un $2\%$ aproximadamente. Con esto en consideración podemos aceptar la hipótesis planteada con anterioridad (al menos desde un punto de vista empírico); el cálculo del Weighted Mid-Price puede llegar a fluctuar en términos de unidades o escala, sin embargo en teoría al menos los dos métodos de cálculo propuestos deberían reflejar la misma información en términos de la microestructura del mercado analizado.
Al igual que con el proceso de prueba que se maneja para el modelo del APT, para estudiar el comportamiento de este modelo de Roll también se estuvo trabajando con una serie de funciones que logren agilizar el proceso de análisis de los resultados.
Entonces, antes de partir directamente a dichos resultados la intención es dar un contexto respecto a la derivación y potencial de aplicación de este modelo. Para ello lo primero que se hace es partir de las siguientes ecuaciones, las cuales definen el precio de transacción esperado.
$$m_{t} = m_{t-1}+u_{t}$$$$p_{t} = m_{t}+Cq_{t}$$donde:
En este sentido $q_{t}$ es un proceso binario con probabilidad de ocurrencia para una venta del $50\%$ y por ende para una compra del complemento $(50\%)$. Es importante mencionar que la aplicación del modelo de Roll se da para mercados no orientados por órdenes, es decir, la interacción entre los inversionistas es directa con un market maker, es por ello que el objetivo final del Roll Model es poder definir un coste de transacción (y por ende un spread) derivado del uso de estas estructuras de mercado. Es a partir de esta circunstancia que el concepto de diferenciales de precio es muy importante $(\Delta p_{t})$.
$$\Delta p_{t} = Cq_{t} - Cq_{t-1} + u_{t}$$$$\Delta p_{t-1} = -Cq_{t-2} + Cq_{t-1} - u_{t}$$Ahora ya con los diferenciales de precio definidos a cada punto del tiempo, es importante recordar que operador es el que nos puede ayudar a encontrar un patrón de reconocimiento entre cada elemento de la serie de tiempo, como sabemos tanto la covarianza como la correlación son conceptos estadísticos que interrelacionan el comportamiento entre $2$ y más variables aleatorias, es por ello que se definen estos para encontrar dicha relación y alcanzar a definir dicho patrón de comportamiento. Si recordamos la covarianza entre dos variables aleatorias se define como:
$$\text{Cov}\big(x,y \big) = E \big[x y \big]$$Por lo tanto para términos de aplicación del problema a modelar tendríamos que:
$$\text{Cov}\big(\Delta p_{t-1} \Delta p_{t} \big) = E \big[\Delta p_{t-1} \Delta p_{t} \big]$$Desarrollando los términos de la multiplicación interna obtenemos que:
$$E \big[\Delta p_{t-1} \Delta p_{t} \big] = E \big[-C^{2}q_{t-1}^{2} \big] + E \big[C^{2}q_{t-1}q_{t-2} \big] + E \big[Cq_{t-1}u_{t-1} \big] + E \big[C^{2}q_{t}q_{t-1} \big] - E \big[C^{2}q_{t}q_{t-2} \big] - E \big[Cq_{t}u_{t-1} \big] + E \big[Cq_{t-1}u_{t} \big] - E \big[Cq_{t-2}u_{t} \big] - E \big[u_{t}u_{t-1} \big]$$Ahora ya con los valores esperados definidos es justamente a este punto donde comienzan a entrar e interacturar las grandes suposiciones sobre las cuales se cimenta el modelo de Roll.
Con todos los supuestos planteados y partiendo del desarrollo para $\text{Cov}\big(\Delta p_{t-1} \Delta p_{t} \big)$, obtenemos finalmente la simplificación final del modelo.
$$\gamma = -C^{2}$$donde:
Ya con la expresión final para el cálculo de coste por transacción para el market maker (el cual por obvias razones se ve cobrado implicitamente para los inversionistas), podemos definir el spread como $2C$, esto para poder tomar ambas operaciones de compra-venta que se sostienen en cada operación. Además de poder definir el spread con la implementación de dicho modelo, tomando en cuenta las dinámicas de la microestructura podemos definir también un valor teórico tanto para Bid como para Ask:
$$\text{Bid} = m_{t} - C$$$$\text{Ask} = m_{t} + C$$En este sentido y conociendo más a detalle la finalidad y aplicaciones propias del modelo de Roll se propone el siguiente foco de análisis para poder contrastar su aplicación con datos de la vida real.
A continuación se mostrará la documentación correspondiente a la función que nos apoyará en el desarrollo y experimentación para el modelo de Roll.
### Let's see docstring for roll model testing function
help(fn.roll_model_check)
Help on function roll_model_check in module functions:
roll_model_check(ob_data: dict, pt_data: pandas.core.frame.DataFrame) -> dict
Test Roll model function. It calculates theoretical spread in order to make a clear comparison between that
value and the observed in real data, It also calculates the probability evolution in sell and buy orders to
check the assumption of independence between order type
Parameters
----------
ob_data:dict (default:None) --> Required parameter
Input data from orderbook, it has to be a dict of data frames with the following structure:
'timestamp': Principal key, correspond to the timestamp associated to each orderbook
'bid_size': First column on each data frame, correspond to the bid volume associated to each bid price order
'bid': Second column of the data frame, correspond to the highest price a buyer is willing to buy
'ask': Third column of the data frame, correspond to the lowest price a seller is willing to sell
'ask_size': Fourth column of the data frame, correspond to the ask volume associated to each ask price order
pt_data:pd.DataFrame (default:None) --> Required parameter
Public trades data frame, it has to follow the next structure:
'timestamp': First column, correspond to the timestamp associated to each registered trade
'price': USD price at which the transaction was made
'amount': Traded volume at a specific price and timestamp
'side': Traded order direction (sell or buy)
Returns
-------
r_data: dict
Return data, it's a dict of different data type with the following structure:
'spread_definition': Data frame with the calculations of theoretical spread, bid and ask
'prob_evolution': Data frame with the probability evolution on sell and buy orders. Just a
sample of 10,000 because time complexity computation
'auto_correlation': Auto correlation between orders (buy and sell)
'total_sell_prob': Frequency probability of total orders of sell
'total_buy_prob': Frequency probability of total orders of buy
References
----------
[1] https://www.statsmodels.org/dev/_modules/statsmodels/tsa/stattools.html
### Let's calculate the metrics for roll model
warnings.filterwarnings("ignore")
pt_data = dt.pt_data
roll_model = fn.roll_model_check(ob_data, pt_data)
print(f'The keys for roll model testing functions are: {roll_model.keys()}')
The keys for roll model testing functions are: dict_keys(['spread_definition', 'prob_evolution', 'auto_correlation', 'total_sell_prob', 'total_buy_prob'])
Al igual que con el modelo de APT las vertientes de análisis para el modelo de Roll pueden ser bastante amplias, al final del día lo que se busca es probarlo en un contexto real, midiendo la efectividad del mismo y el propio cumplimiento de sus supuestos. Para este caso de estudio en particular lo que se propone es definir a $m_{t}$ como el Simple Mid-Price un métrica que como observamos con anterioridad no fluctúa tanto por ventanas de tiempo muy pequeñas.
La idea principal será definir el spread teórico que esta asociado o implicito en la serie histórica de esos precios (concretamente en el diferencial de estos), para ver no solo que tan lejano o cercano se encuentra en relación al spread real observado de los datos (ask - bid), también para poder notar su evolución en el tiempo y comprobar si realmente se puede considerar un valor constante.
Recordando un poco respecto a las ecuaciones que definen al modelo de Roll definimos al spread como $2C$, donde:
$$C = \sqrt{-\gamma}$$Y sabiendo que $\gamma=\text{Cov}\big(\Delta p_{t-1} \Delta p_{t} \big)$, podemos decir que el cálculo teórico del spread y los costes de transacción implicitos que son cobrados por los "dealers" depende de cierto grado de relación entre los diferenciales de los precios.
Para poder calcular esta situación se define el Simple Mid-Price del top of the book de cada libro de órdenes para sobre estos valores definir dicho valor.
### Let'see the data frame with the definition of theoretical spread vs real
print(f'''The mean real observed spread is: {round(roll_model['spread_definition']['real_spread'].mean(),4)}
On the other hand the mean theoretical spread is: {round(roll_model['spread_definition']['theoretical_spread'].mean(),4)}
The convergence for theoretical spread is: {round(roll_model['spread_definition']['theoretical_spread'].tail(1)[0],4)}
Finally this values match on: {sum(
roll_model['spread_definition']['real_spread']==roll_model['spread_definition']['theoretical_spread'])} cases''')
roll_model['spread_definition'].iloc[:,0:8].head()
The mean real observed spread is: 3.9463 On the other hand the mean theoretical spread is: 0.2643 The convergence for theoretical spread is: 0.07 Finally this values match on: 0 cases
| bid_size | bid | ask | ask_size | mid_price | real_spread | theoretical_spread | spread_diff | |
|---|---|---|---|---|---|---|---|---|
| Time | ||||||||
| 2021-07-05 13:06:46.571000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 |
| 2021-07-05 13:06:47.918000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 |
| 2021-07-05 13:06:49.414000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 |
| 2021-07-05 13:06:51.077000+00:00 | 14.605659 | 28275.0 | 28278.0 | 0.011063 | 28276.5 | 3.0 | 1.885618 | 1.114382 |
| 2021-07-05 13:06:52.426000+00:00 | 14.605659 | 28275.0 | 28278.0 | 0.011063 | 28276.5 | 3.0 | 2.581989 | 0.418011 |
Del resultado obtenido en cuanto al contraste teórico propuesto por el modelo de Roll respecto al valor real observado para el concepto del spread podemos percatarnos de que existe un diferenciación importante entre los mismos. Por una parte la evolución del spread teórico es mucho más fluctuante y tiende a inclinarse por un valor cercano a $0$ (situación que se comprobará graficamente), mientras que el valor promedio del spread real observado es considerablemente mayor en proporción al teórico.
Otra situación que resulta sumamente interesante es el hecho de que prácticamente no existió ningún punto en el tiempo donde el spread teórico fuera exactamente igual al spread observado, si llegan a estar cerca pero nunca se definen igual, lo cual tiene sentido si tomamos en cuenta que los spread reales solo se definen como enteros mientras que los teóricos sí llegan a tomar valores flotantes. De igual manera tanto la evolución como convergencia de dichos valores se percibirán de una mejor manera en el apartado de Results and Visual Resources.
Según el modelo de Roll la definición teórica del Bid se define como:
$$\text{Bid} = m_{t} - C$$Donde $m_{t}$ representa al Simple Mid-Price.
### Let'see the data frame with the definition of theoretical bid vs real
print(f'''The mean real observed bid is: {round(roll_model['spread_definition']['bid'].mean(),4):,}
On the other hand the mean theoretical bid is: {round(roll_model['spread_definition']['theoretical_bid'].mean(),4):,}
Finally this values match on: {sum(
roll_model['spread_definition']['bid']==roll_model['spread_definition']['theoretical_bid'])} cases''')
roll_model['spread_definition'].head()
The mean real observed bid is: 28,349.9055 On the other hand the mean theoretical bid is: 28,351.8086 Finally this values match on: 0 cases
| bid_size | bid | ask | ask_size | mid_price | real_spread | theoretical_spread | spread_diff | theoretical_bid | theoretical_ask | |
|---|---|---|---|---|---|---|---|---|---|---|
| Time | ||||||||||
| 2021-07-05 13:06:46.571000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 | 28272.429971 | 28272.570029 |
| 2021-07-05 13:06:47.918000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 | 28272.429971 | 28272.570029 |
| 2021-07-05 13:06:49.414000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 | 28272.429971 | 28272.570029 |
| 2021-07-05 13:06:51.077000+00:00 | 14.605659 | 28275.0 | 28278.0 | 0.011063 | 28276.5 | 3.0 | 1.885618 | 1.114382 | 28276.429971 | 28276.570029 |
| 2021-07-05 13:06:52.426000+00:00 | 14.605659 | 28275.0 | 28278.0 | 0.011063 | 28276.5 | 3.0 | 2.581989 | 0.418011 | 28276.429971 | 28276.570029 |
Para el caso del Bid price podemos percatarnos de que si bien existe cierto nivel de diferencia, al menos desde una perspectiva de promedios la fluctuación entre el real observado y el teórico no parece ser tanta. La realidad es que la definición teórica del precio del Bid en este modelo toma como componente principal el Simple Mid-Price (el cual pondera de igual manera las voluntades de compra y de venta en los inversionistas) es por ello que la diferencia principal que existe de un Bid a otro se debe en su mayoría al no empate frente a los spreads del modelo y el real.
La evolución del valor teórico contra el real observado en términos del Bid price se logrará percibir de una mejor manera a través del apartado de visualizaciones.
Según el modelo de Roll la definición teórica del Ask se define como:
$$\text{Ask} = m_{t} + C$$Donde $m_{t}$ representa al Simple Mid-Price.
### Let'see the data frame with the definition of theoretical ask vs real
print(f'''The mean real observed ask is: {round(roll_model['spread_definition']['ask'].mean(),4):,}
On the other hand the mean theoretical ask is: {round(roll_model['spread_definition']['theoretical_ask'].mean(),4):,}
Finally this values match on: {sum(
roll_model['spread_definition']['ask']==roll_model['spread_definition']['theoretical_ask'])} cases''')
roll_model['spread_definition'].head()
The mean real observed ask is: 28,353.8517 On the other hand the mean theoretical ask is: 28,351.9486 Finally this values match on: 0 cases
| bid_size | bid | ask | ask_size | mid_price | real_spread | theoretical_spread | spread_diff | theoretical_bid | theoretical_ask | |
|---|---|---|---|---|---|---|---|---|---|---|
| Time | ||||||||||
| 2021-07-05 13:06:46.571000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 | 28272.429971 | 28272.570029 |
| 2021-07-05 13:06:47.918000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 | 28272.429971 | 28272.570029 |
| 2021-07-05 13:06:49.414000+00:00 | 0.000400 | 28270.0 | 28275.0 | 0.025405 | 28272.5 | 5.0 | 0.000000 | 5.000000 | 28272.429971 | 28272.570029 |
| 2021-07-05 13:06:51.077000+00:00 | 14.605659 | 28275.0 | 28278.0 | 0.011063 | 28276.5 | 3.0 | 1.885618 | 1.114382 | 28276.429971 | 28276.570029 |
| 2021-07-05 13:06:52.426000+00:00 | 14.605659 | 28275.0 | 28278.0 | 0.011063 | 28276.5 | 3.0 | 2.581989 | 0.418011 | 28276.429971 | 28276.570029 |
Para el caso de contraste entre el Ask teórico y el real, se puede comenzar a intuir una situación muy similar a la observada para el caso del Bid, realmente la fluctuación se da gracias a la diferencia entre los spreads.
Uno de los supuestos más grandes sobre los cuales se define el modelo de Roll es el de no correlación entre los tipos de orden, es decir, que exista independencia entre si se transacciona una orden de venta o una orden compra. Si recordamos, matemáticamente dicho supuesto se denotaba por $q_{t}$, lo cual representa la probabilidad de ocurrencia de una compra o venta, y este proceso sigue una distribución binomial que asigna la misma probabilidad a cada tipo de orden.
El poder definir la independencia otorga ciertas propiedades estadísticas que ayudan a la derivación del modelo de Roll: $E\big[q_{t-1}q_{t} \big]=0$.
Sin embargo, el decir que hay independencia entre los tipos de orden de alguna manera también implicaría decir que no existen periodos de tendencia en el valor del activo de interés, ya que se estaría llegando a la conclusión de que el precio del mismo (en la mayoría de los casos) simplemente sigue un proceso aleatorio, y dichos cambios no se estarían asignando a los patrones de compra o venta por parte de los inversionitas. Ante esta situación es que se decide probar la factibilidad de este supuesto del modelo, para ello se estará utilizando el conjunto de datos correspondiente a los Public Trades principalmente porque esta estructura de datos ya nos presenta una consolidación de las operaciones, por ende ya existe un registro de si se reportó una compra o en su defecto una venta.
Como bien se comentó anteriormente, el supuesto de independencia entre tipos de órdenes puede tener sus implicaciones en la definición del precio y los costes de transacción para los market makers. Lo que se buscará analizar en este sentido será como es que evoluciona la probabilidad asociada a una compra o venta desde un enfoque frecuentista, esto con la intención de poder definir si es que los valores de probabilidad realmente convergen a un $50\%$.
### Let's see the probability evolution, autocorrelation and total probabilities
print(f'''The total probability associated to a buy trade is: {roll_model['total_buy_prob']*100}%
The total probability associated to a sell trade is: {round(roll_model['total_sell_prob']*100, 4)}%
Finally the autocorrelation between orders is of: {round(roll_model['auto_correlation']*100, 2)}%''')
roll_model['prob_evolution'].head()
The total probability associated to a buy trade is: 50.23% The total probability associated to a sell trade is: 49.77% Finally the autocorrelation between orders is of: 56.89%
| timestamp | price | amount | side | prob_sell | prob_buy | |
|---|---|---|---|---|---|---|
| 0 | 2022-05-10 08:08:07 | 31702.47 | 0.00035 | sell | 1.0000 | 0.0000 |
| 1 | 2022-05-10 08:08:07 | 31702.48 | 0.00263 | buy | 0.5000 | 0.5000 |
| 2 | 2022-05-10 08:08:07 | 31702.48 | 0.00631 | buy | 0.3333 | 0.6667 |
| 3 | 2022-05-10 08:08:07 | 31702.48 | 0.00264 | buy | 0.2500 | 0.7500 |
| 4 | 2022-05-10 08:08:07 | 31702.48 | 0.01146 | buy | 0.2000 | 0.8000 |
De los resultados impresos por parte de la celda anterior se descata bastante el hecho de que la convergencia final a las probabilidades de una transacción de compra o de venta en efecto tiende a centrarse en un $50\%$ (al menos con la microestructura analizada), sin embargo aquí es donde empieza lo interesante, ya que si bien en términos absolutos la probabilidad de que se de una orden de compra o una orden de venta es practicamente la misma, no podemos decir que existe independencia en la consecutividad de las mismas, al contrario, se logra percibir que existe una relación lineal directa relativamente fuerte entre las órdenes (aproximadamente un $57\%$), esto rompe totalmente con el supuesto de independencia previamente mencionado, ya que al menos con los datos analizados si se logra percibir que existe una tendencia en las órdenes (compras siguen compras y ventas siguen ventas).
Sin duda alguna las visualizaciones forman una parte fundamental en el proceso de abstracción de la información, los apoyos gráficos no solo nos ayudan a ver el panorama desde una perspectiva general, también da la oportunidad de observar tendencias en el comportamiento de los procesos analizados. En este sentido la idea de este apartado es proveer de una serie de apoyos visuales que ayuden a consolidar los conceptos previamente mencionados en los apartados de pruebas en los modelos de alta frecuencia.
La idea principal para todas las gráficas será la de un análisis de serie de tiempo, se quiere observar la evolución y desempeño correspondiente a cada modelo. Con la intención de definir de una mejor manera los parámetros de entrada a las funciones definidas así como el alcance y salidas de cada una de ellas, se mostrará la documentación correspondiente a cada una de ellas:
### Let's see docstring for APT charts
help(vz.plot_stacked_bar)
Help on function plot_stacked_bar in module visualizations:
plot_stacked_bar(exp_df: pandas.core.frame.DataFrame, minutes: int = None)
Stacked bar plot generator for the experiments proposed in APT model testing
Parameters
----------
exp_df: DataFrame (default:None) --> Required parameter
Principal input data, correspond to a data frame where Exp. 1 and Exp. 2 where developed, it has to follow the
next structure:
'Time': Datetime data frame index consolidated by minute
'Exp 1': Number of occurrences where mid-price(t) == mid-price(t+1)
'Exp 2': Number of occurrences where mid-price(t) != mid-price(t+1)
'P. Exp 1': Proportion of observations that satisfies the first experiment
'P. Exp 2': Proportion of observations that satisfies the second experiment
minutes: Integer (default:None) --> Optional parameter
Number of minutes that the user want to see in the chart (it has to follow the time frame structure of data)
Returns
-------
fig: Figure
Plotly figure containing a stacked bar chart with the proportion of the developed experiments in APT model
testing
References
----------
[1] https://plotly.com/python/horizontal-bar-charts/
### Let's see docstring for Roll Model
help(vz.plot_teo_spread)
Help on function plot_teo_spread in module visualizations:
plot_teo_spread(spread_data: pandas.core.frame.DataFrame)
Line charts for Roll model testing. It can be for theoretical spread, bid and ask comparison with real
observed data
Parameters
----------
spread_data: DataFrame (default:None) --> Required parameter
Principal input data, correspond to a data frame with the definition of theoretical spread, bid and ask,
it has to follow the next structure:
'Time': Datetime data frame index, defined for each orderbook
'bid_size': Bid volume (top of the book)
'bid': Bid price in USD (top of the book)
'ask': Ask price in USD (top of the book)
'ask_size': Ask volume (top of the book)
'mid_price': Mid-price in USD for each orderbook
'real_spread': Real observed spread between bid and ask (top of the book)
'theoretical_spread': Calculated spread with Roll model definition
'spread_diff': Difference between real and theoretical spread
'theoretical_bid': Calculated bid with Roll model definition (Mid-price - Theoretical spread)
'theoretical_ask': Calculated ask with Roll model definition (Mid-price + Theoretical spread)
Returns
-------
fig_spread: Figure
Plotly figure containing a line chart where it's compared theoretical vs real spread
fig_diff: Figure
Plotly figure containing a bar chart where it's compared the difference between theoretical and
real spread
fig_bid: Figure
Plotly figure containing a line chart where it's compared theoretical vs real bid
fig_ask: Figure
Plotly figure containing a line chart where it's compared theoretical vs real ask
fig_theo: Figure
Plotly figure containing a line chart where it's compared all theoretical metrics
fig_real: Figure
Plotly figure containing a line chart where it's compared all observed metrics
References
----------
[1] https://plotly.com/python/line-charts/
### Let's see docstring for Probability evolution in roll model assumption (indepenedence in orders)
help(vz.plot_prob_evo)
Help on function plot_prob_evo in module visualizations:
plot_prob_evo(pt_data: pandas.core.frame.DataFrame)
Line chart to describe the evolution of probability between orders and their changes. The goal of this
plot is to show in the probability of occurrence converge to a specific value
Parameters
----------
pt_data: DataFrame (default:None) --> Required parameter
Public trades data frame, it has to follow the next structure:
'timestamp': First column, correspond to the timestamp associated to each registered trade
'price': USD price at which the transaction was made
'amount': Traded volume at a specific price and timestamp
'side': Traded order direction (sell or buy)
'prob_sell': Probability evolution of sell orders
'prob_buy': Probability evolution of buy orders
'direction': -1 if there is a sell order and 1 for buy order
Returns
-------
fig: Figure
Plotly figure containing a line chart showing the probability convergence between orders
References
----------
[1] https://plotly.com/python/line-charts/
Como se recordará, la experimentación llevada a cabo para el modelo del APT consistió en comprobar cuantas veces es que durante el cambio en el tiempo (por minuto) el precio del activo subyacente sigue un proceso tipo martingala. En este sentido la visualización más conveniente consiste en una gráfica de barras apiladas donde se pueda mostrar en términos de frecuencia cuantas veces se cumple el supuesto del modelo (eje $y$) para cada punto en el tiempo (eje $x$).
La programación de dicho graficador permite también al usuario poder definir cuantos minutos de información quiere ver en su pantalla, la idea es que siempre irá recorriendo desde el primer minuto registrado hasta la cantidad que el usuario indique. Para términos prácticos y de presentación de resultados en este notebook se opta por solo visualizar $30$ minutos pero cada quien puede definir la cantidad de tiempo que quiere observar.
### Let's see how stands the proportion of times where the price follows a martingale process (all orders)
vz.plot_stacked_bar(apt_all['simple_mid_price'], minutes=30)
Esta primera gráfica corresponde al resultado del primer enfoque de análisis propuesto, el cual incluye prácticamente todas las órdenes de cada orderbook disponible (para el simple mid-price). La idea que se busca visualizar a este punto es lo que se estuvo comentando en el apartado correspondiente, vemos como es que existe una mayor proporción de veces en las que el precio sigue un proceso tipo martingala en comparación al uso de órdenes top of the book (siguiente gráfica).
### Let's see how stands the proportion of times where the price follows a martingale process (tob orders)
vz.plot_stacked_bar(apt_tob['simple_mid_price'], minutes=30)
Para el caso de esta segunda visualización de los experimentos com el modelo de APT podemos comprobar visualmente como es que la probabilidad o en general el número de veces en que cambia el precio de un libro de órdenes a otro es más frecuente en comparativa con el uso de todas las órdenes de cada orderbook, al menos desde la perspectiva del Simple Mid-Price.
Para la visualización de los experimentos y pruebas realizadas sobre el modelo de Roll, se provee una serie de gráficas que siguen una estructura similar, todas representan una serie de tiempo, la diferencia radica en la información que buscan reflejar. A continuación se presentan cada una de ellas.
### Let's see how theoretical spread stands againts real observed spread
vz.plot_teo_spread(roll_model['spread_definition'])['spread']
De la gráfica de evolución tanto del spread teórico obtenido por el modelo como del real observado se puede apreciar a simple vista la disparidad existente entre ellos, si bien en un principio de la serie parecen manejar valores relativamente similares, después de cierto tiempo se logra percibir como es que el teórico se encuentra camino a definirse constante, mientrás que por el otro lado, el spread real continua con su tendencia aleatoria.
### Let's see how is the difference between spreads (theoretical vs observed)
vz.plot_teo_spread(roll_model['spread_definition'])['diff']
La gráfica observada respecto a los diferenciales en los spreads (reales vs teóricos), realmente solo es una ilustración y un pequeño "zoom" al comportamiento de fluctuación en los primeros $20$ libros de órdenes, que coincide justamente con el punto en el tiempo en el que el spread teórico incluso logra estar por encima del real. Después de esta serie solo se observarían barras azules que favorecerán a la magnitud del spread real, ya que como se recordará el spread teórico tien a un valor constante.
La intención de este apartado de visualizaciones es poder observar como es que se definen las métricas (bid y ask) teóricas a través del tiempo en términos comparativos (tanto entre ellas como contrastando con los datos reales).
### Let's see how all of our theoretical metrics are defined
vz.plot_teo_spread(roll_model['spread_definition'])['theo']
A simple vista pareciera ser que esta serie de tiempo contiene una gráfica única y exclusivamente para el Mid-Price, sin embargo es importante recordar que el spread teórico obtenido por el modelo de Roll es sumamente pequeño en escala con el rango de precios del BTCUSDT, es por eso que no se alcanza a distinguir una diferencia entre los valores de Bid y Ask.
### Let's see how the bid value behave
vz.plot_teo_spread(roll_model['spread_definition'])['bid']
Contrastando ahora el Bid teórico con el Bid real observado podemos observar como es que a lo largo de la serie, el teórico pareciera estar sobre estimado las voluntades reales de compra por parte de los inversionistas.
### Let's see how the ask value behave
vz.plot_teo_spread(roll_model['spread_definition'])['ask']
Para el caso de la comparativa entre el Ask teórico y el real observado sucede una situación inversa a con el Bid, aquí el valor teórico obtenido parece estar bajando sistemáticamente las voluntades de venta en los inversionistas. Estas dos situaciones tienen bastante sentido si recordamos que el spread teórico es muy pequeño, entonces eso provoca que los rangos entre compradores y vendedores se vean considerablemente reducidos.
### Let's see how all of our real observed metrics are defined
vz.plot_teo_spread(roll_model['spread_definition'])['real']
Finalmente al contrastar todas las métricas reales y observadas ya se empieza a distinguir y diferenciar cada serie de tiempo. Algo interesante a resaltar en esta grafica es que a lo largo del tiempo el Mid-Price se ve posicionado constantemente en el centro entre Bid y Ask, esto lo que nos indica es que realmente las voluntades de compra y venta se ven parecidas a lo largo del periodo analizado.
Para finalizar con las visualizaciones correspondientes al modelo de Roll se presenta una gráfica de convergencia de probabilidad para la órdenes de compra y venta respectivamente. La idea es tratar de visualizar como es que esta probabilidad va evolucionando hasta converger al punto de paridad del $50\%$.
Es importante mencionar que por temas de complejidad computacional solo se están mostrando $10,000$ de la totalidad de la serie disponible que se encuentra sobre los $260,000$. Entonces, empiricamente en el apartado del modelo de Roll ya se demostró que sí existe paridad, ahora solo se mostrará la evolución de la misma.
### Let's see the probability evolution within orders type
vz.plot_prob_evo(roll_model['prob_evolution'])
Como se pudo observar durante el desarrollo del proyecto, ambos modelos de alta frecuencia cuentan con ciertas consideraciones que realzan sus virtudes y desventajas, sin embargo, a opinión personal considero los siguientes puntos mas destacables del análisis realizado:
Para el caso del modelo de APT:
Llama bastante la atención como es que la estructura de análisis puede llegar a variar tanto el resultado. Las $2$ perspectivas propuestas (usar todas las órdenes disponibles y solamente utilizar las órdenes del top of the book) arrojaron resultados muy interesantes, los cuales ayudan a afianzar de una mejor manera los conceptos y sobre todo a entender de manera más integral la microestructura del mercado analizado.
Para el caso del modelo de Roll:
Recordando que el objetivo principal del modelo de Roll es definir el coste de transacción implicito en transacciones a través de market makers, se puede decir lo siguiente respecto al contraste de dicho valor con los observados en la vida real.
[1] Munnoz, 2020. Python project template. https://github.com/iffranciscome/python-project. (2021).
[2] Hasbrouck, J. (2007). Empirical Market Microstructure: The Institutions, Economics and Econometrics of Securities Trading. Nueva York, EUA: Oxford University
[3] Munk, C. (2013). Financial Asset Pricing Theory. Oxford, Inglaterra: Oxford University.